package cc.vv.lkimagecomponent2.compressplugin;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import cc.vv.lkimagecomponent2.LKImage;


/**
 * 压缩功能。此类中所有压缩功能都是异步调用，主线程回调。
 *
 * @author dupeng dupeng@vv.cc
 * @date 2018/3/15
 */
public class CompressPlugin implements Handler.Callback {
    private static final String PATH = "compress";

    private static CompressPlugin instance;

    private static final int MSG_COMPRESS_SUCCESS = 0;
    private static final int MSG_COMPRESS_START = 1;
    private static final int MSG_COMPRESS_ERROR = 2;

    public static CompressPlugin getInstance() {
        if (instance == null) {
            synchronized (CompressPlugin.class) {
                if (instance == null) {
                    instance = new CompressPlugin();
                }
            }
        }
        return instance;
    }

    private H mHandler = new H(this);

    private static class H extends Handler {
        public H(Callback callback) {
            super(Looper.getMainLooper(), callback);
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg == null || msg.obj == null) {
            return false;
        }
        try {
            Data data = (Data) msg.obj;
            if (data.listener == null) {
                return false;
            }

            switch (msg.what) {
                case MSG_COMPRESS_START:
                    data.listener.onStart();
                    break;
                case MSG_COMPRESS_SUCCESS:
                    data.listener.onSuccess(data.result);
                    break;
                case MSG_COMPRESS_ERROR:
                    data.listener.onError();
                    break;
                default:
                    break;
            }
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 压缩。进行宽高压缩。压缩到指定大小以下
     *
     * @param imgPath
     * @param maxKBSize 要保存的文件最大大小：单位KB
     * @param listener  回调。主线程回调
     */
    public void compress(final String imgPath, final long maxKBSize, final OnCompressListener<File> listener) {
        if (TextUtils.isEmpty(imgPath)) {
            return;
        }

        final long maxByteSize = maxKBSize * 1024;

        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_START, new Data(listener, null)));

                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inJustDecodeBounds = true;
                    BitmapFactory.decodeFile(imgPath, options);
                    int imgWidth = options.outWidth;
                    int imgHeight = options.outHeight;
                    options.inSampleSize = computeSize(imgWidth, imgHeight);

                    options.inJustDecodeBounds = false;
                    Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);

                    if (baos.size() <= maxByteSize) {
                    } else {
                        baos.reset();
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 0, baos);
                        if (baos.size() >= maxByteSize) {
                        } else {
                            // find the best quality using binary search
                            int st = 0;
                            int end = 100;
                            int mid = 0;
                            while (st < end) {
                                mid = (st + end) / 2;
                                baos.reset();
                                bitmap.compress(Bitmap.CompressFormat.JPEG, mid, baos);
                                int len = baos.size();
                                if (len == maxByteSize) {
                                    break;
                                } else if (len > maxByteSize) {
                                    end = mid - 1;
                                } else {
                                    st = mid + 1;
                                }
                            }
                            if (end == mid - 1) {
                                baos.reset();
                                bitmap.compress(Bitmap.CompressFormat.JPEG, st, baos);
                            }
                        }
                    }

                    bitmap.recycle();
                    File result = saveBitmap(baos, imgPath, bitmap.getWidth(), bitmap.getHeight());
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_SUCCESS, new Data(listener, result)));
                } catch (
                        OutOfMemoryError e) {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_ERROR, new Data(listener, null)));
                }
            }
        });
    }

    /**
     * 压缩。只进行宽高压缩。质量压缩60.
     *
     * @param imgPath  要压缩的图片路径
     * @param listener 回调。主线程回调
     */
    public void compress(final String imgPath, final OnCompressListener<File> listener) {
        if (TextUtils.isEmpty(imgPath)) {
            return;
        }

        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_START, new Data(listener, null)));
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inJustDecodeBounds = true;
                    BitmapFactory.decodeFile(imgPath, options);
                    int imgWidth = options.outWidth;
                    int imgHeight = options.outHeight;
                    options.inSampleSize = computeSize(imgWidth, imgHeight);

                    options.inJustDecodeBounds = false;

                    Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 60, baos);
                    bitmap.recycle();

                    File result = saveBitmap(baos, imgPath, bitmap.getWidth(), bitmap.getHeight());
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_SUCCESS, new Data(listener, result)));
                } catch (
                        OutOfMemoryError e) {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_ERROR, new Data(listener, null)));
                }
            }
        });
    }

    /**
     * 压缩bitmap。
     * 将大小看情况压缩，
     * 质量压缩60。
     *
     * @param src
     * @param listener
     */
    public void compress(final Bitmap src, final OnCompressListener<Bitmap> listener) {
        if (isEmptyBitmap(src)) {
            return;
        }

        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_START, new Data(listener, null)));
                    int i = computeSize(src.getWidth(), src.getHeight());
                    Bitmap ret = null;
                    Matrix matrix = new Matrix();
                    matrix.setScale(1f / i, 1f / i);
                    ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);

                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ret.compress(Bitmap.CompressFormat.JPEG, 60, baos);
                    ret.recycle();

                    byte[] bytes = baos.toByteArray();
                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_SUCCESS, new Data(listener, bitmap)));
                } catch (OutOfMemoryError e) {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_ERROR, new Data(listener, null)));
                }
            }
        });
    }

    /**
     * 压缩到指定宽高。不进行质量压缩
     *
     * @param src       The source of bitmap.
     * @param newWidth  The new width.
     * @param newHeight The new height.
     */

    public void compress(final Bitmap src,
                         final int newWidth,
                         final int newHeight, final OnCompressListener<Bitmap> listener) {
        if (isEmptyBitmap(src)) {
            return;
        }
        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_START, new Data(listener, null)));
                    Bitmap ret = Bitmap.createScaledBitmap(src, newWidth, newHeight, true);
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_SUCCESS, new Data(listener, ret)));
                } catch (OutOfMemoryError e) {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_ERROR, new Data(listener, null)));
                }
            }
        });
    }

    /**
     * 只质量压缩到指定大小。不进行宽高压缩。
     *
     * @param src      The source of bitmap.
     * @param quality  0-100:最低-最高
     * @param listener 回调。主线程回调
     */
    public void compressByQuality(final Bitmap src, final int quality, final OnCompressListener<Bitmap> listener) {
        if (isEmptyBitmap(src) && (quality < 0 || quality > 100)) {
            return;
        }

        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_START, new Data(listener, null)));
                    int i = computeSize(src.getWidth(), src.getHeight());
                    Bitmap ret = null;
                    Matrix matrix = new Matrix();
                    matrix.setScale(1f / i, 1f / i);
                    ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);

                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ret.compress(Bitmap.CompressFormat.JPEG, quality, baos);
                    ret.recycle();

                    byte[] bytes = baos.toByteArray();
                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_SUCCESS, new Data(listener, bitmap)));
                } catch (OutOfMemoryError e) {
                    mHandler.sendMessage(getMessage(MSG_COMPRESS_ERROR, new Data(listener, null)));
                }
            }
        });
    }


    private static boolean isEmptyBitmap(final Bitmap src) {
        return src == null || src.getWidth() == 0 || src.getHeight() == 0;
    }


    /**
     * Return the sample size.
     *
     * @param options   The options.
     * @param maxWidth  The maximum width.
     * @param maxHeight The maximum height.
     * @return the sample size
     */
    private static int calculateInSampleSize(final BitmapFactory.Options options,
                                             final int maxWidth,
                                             final int maxHeight) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;
        while ((width >>= 1) >= maxWidth && (height >>= 1) >= maxHeight) {
            inSampleSize <<= 1;
        }
        return inSampleSize;
    }

    /**
     * 合适的压缩比。仿微信算法。
     *
     * @param srcWidth
     * @param srcHeight
     * @return
     */
    private int computeSize(int srcWidth, int srcHeight) {
        srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
        srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;

        int longSide = Math.max(srcWidth, srcHeight);
        int shortSide = Math.min(srcWidth, srcHeight);

        float scale = ((float) shortSide / longSide);
        if (scale <= 1 && scale > 0.5625) {
            if (longSide < 1664) {
                return 1;
            } else if (longSide >= 1664 && longSide < 4990) {
                return 2;
            } else if (longSide > 4990 && longSide < 10240) {
                return 4;
            } else {
                return longSide / 1280 == 0 ? 1 : longSide / 1280;
            }
        } else if (scale <= 0.5625 && scale > 0.5) {
            return longSide / 1280 == 0 ? 1 : longSide / 1280;
        } else {
            return (int) Math.ceil(longSide / (1280.0 / scale));
        }
    }

    private Message getMessage(int what, Data data) {
        Message message = mHandler.obtainMessage(what);
        message.obj = data;
        return message;
    }

    private class Data<T> {
        private OnCompressListener listener;
        private T result;

        public Data(OnCompressListener<T> listener, T result) {
            this.listener = listener;
            this.result = result;
        }

    }

    /**
     * 保存到本地
     *
     * @param baos
     * @param imgPath
     */
    private File saveBitmap(ByteArrayOutputStream baos, String imgPath, int imgWidth, int imgHeight) {
        String fileName;
        fileName = System.currentTimeMillis() + "compress.jpg";

        //图片压缩保存路径
        String rootPath = LKImage.getContext().getExternalFilesDir(PATH).getAbsolutePath();
        File folder = new File(rootPath);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        File imgFile = new File(folder, fileName);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(imgFile);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            bos.write(baos.toByteArray());
            bos.flush();
            return imgFile;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (baos != null) {
                    baos.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
        return null;
    }
}
